import http from "http";
import https from "https";
// 负责解压
import zlib from "zlib";
import Response from "./response";
import Cookie from "./cookie";
import RequestOption from "./request-option";
import Download from "./download/download";
import Frequency from "./frequency";
import logc from "@baige/logc";
import Hook from "./hook";


/**
 * 请求类
 */
class Cjc {
  /**
   * 很少用到
   */
  public WWW_FORM = "application/x-www-form-urlencoded";
  /**
   * 文件上传必备
   */
  public FORM_DATA = "multipart/form-data";
  /**
   * 最常用的POST请求方式
   */
  public JSON = "application/json";

  /**
   * 全局请求头
   */
  public globalHeaders = {};
  /**
   * 全局超时时间（单位：秒） 默认1分钟
   */
  public globalTimeout = 60;
  /**
   * 全局钩子函数
   */
  public globalHook: typeof Hook.typeof = new Hook();
  /**
   * 全局cookie
   */
  public globalCookie: typeof Cookie.typeof = new Cookie();
  /**
   * 全局请求频率,防止爬取太快被封ip，单位秒
   */
  private globalFrequency: typeof Frequency.typeof = new Frequency();
  /**
   * 局部延时对象组
   */
  private localFrequencyMap: {
    [key: string]: typeof Frequency.typeof
  } = {};

  /**
   * 正则匹配组
   */
  private localRegexFrequencyMap: {
    [key: string]: typeof Frequency.typeof
  } = {};

  /**
   * 设置全局超时时间
   * @param s 
   */
  public setGlobalFrequency(s: number) {
    this.globalFrequency.frequency = s;
  }

  /**
   *  设置正则匹配
   */
  public setLocalRegexFrequency(regex: RegExp, s: number) {
    this.localRegexFrequencyMap[regex.source] = new Frequency();
    this.localRegexFrequencyMap[regex.source].frequency = s;
    this.localRegexFrequencyMap[regex.source].lastTimeRequestTime = 0;
  }

  /**
   * 设置路径匹配
   */
  public setPathFrequency(path: string, s: number) {
    this.localFrequencyMap[path] = new Frequency();
    this.localFrequencyMap[path].frequency = s;
    this.localFrequencyMap[path].lastTimeRequestTime = 0;
  }

  /**
   * 请求参数初始化，以及参数合并
   * @param option 
   * @returns 
   */
  public async request(option: typeof RequestOption.typeof): Promise<typeof Response.typeof> {
    // 把普通的对象转成真正的请求参数类
    option = new RequestOption(option as any);
    if (!(option.cookie instanceof Cookie)) {
      // cookie被篡改了
      throw new Error("option.cookie是cookie对象,如需传递字符串cookie,请放到option.headers.cookie中")
    }
    // 合并请求头
    option.headers = Object.assign(this.globalHeaders, option.headers);
    // 合并全局cookie
    option.cookie.mergeCookieTree(this.globalCookie.getCookieTree());
    // 合并传递的cookie树
    option.cookie.mergeCookieTree(option.cookieTree);
    // 合并单次cookie
    option.cookie.add(option.url, option.headers.cookie as string, option);
    // 处理代理
    option.handleProxy();
    // 先执行一下请求前钩子函数
    this.globalHook.beforeRequest(option);
    // 开始执行
    let res = await this.delayActuator(option);
    // 请求后钩子函数
    this.globalHook.afterRequest(res);
    return res;
  }

  /**
   * 真正进行运行的请求发送的执行器
   */
  public executor(option: typeof RequestOption.typeof, _message = "请求:"): Promise<typeof Response.typeof> {
    // 每次执行都需重置一次cookie，确保cookie为最新状态
    option.headers.cookie = option.cookie.encapsulation(option.url);
    logc.info(option.logs, _message, option.method + ":" + option.url);
    if (option.download) {
      return Download.download(option);
    }
    return new Promise(async (resolve, reject) => {
      let send = option.handleParameter();

      // 解析好了各个参数
      let req = (option.url.startsWith("https") ? https : http).request(option.url, option, async (res: http.IncomingMessage) => {
        // 新cookie放到集合中
        option.cookie.add(option.url, res, option);
        // 处理返回状态
        switch (res.statusCode) {
          case 301:
          case 302: {
            // 开始重定向,重新向都是get请求
            option.method = "get";
            // 生成重定向地址
            option.url = option.generateRedirectAddress(res);
            // 给false是为了不让cookie进行重新添加
            return await this.executor(option, "开始重定向 ").then(resolve).catch(reject)
          }
          case 500:
          case 200:
          default: {
            // 请求成功了，开始解析参数了
            await this.parsData(res, option).then(resolve as any).catch(reject);
          }
        }
      });
      try {
        // 监控异常
        req.on("error", reject);
        // 设置超时时间
        req.setTimeout((option.timeout || this.globalTimeout) * 1000);
        // 发送Post请求参数 有值无值发一波
        // 结束请求
        await send(req);
      } catch (err) {
        // 判断是否已发送
        if (req.writableEnded != true) {
          req.end();
        }
        reject(err);
      }
    });
  }



  /**
   * 解析状态码200的资源
   * @param res 
   * @param option 
   * @returns 
   */
  public parsData(res: http.IncomingMessage, option: typeof RequestOption.typeof) {
    let respons = new Response();
    respons.res = res;
    respons.statusCode = res.statusCode;
    respons.headers = res.headers;
    respons.cookie = option.cookie.encapsulation(option.url);
    respons.cookieTree = option.cookie.getCookieTree();
    return new Promise((resolve, reject) => {
      let html: Buffer[] = [];
      // 判断是否需要原始的
      if (option.original) return resolve(respons);
      res.on("data", (a: Buffer) => { html.push(a); });
      res.on("end", async () => {
        // 合并数据
        respons.buffer = Buffer.concat(html);
        try {
          // 判断数据是否经过压缩
          if (res.headers["content-encoding"]) {
            zlib.gunzip(respons.buffer, (err, result) => {
              if (err) return reject(err);
              // 解压后的数据
              respons.buffer = result;
              if (!option.parse) {
                // 不允许解析直接返回数据
                return resolve(respons);
              }
              respons.data = this.serializingData(res, result, option);
            });
          } else if (option?.parse) {
            // 需要运行进行数据解析
            respons.data = this.serializingData(res, respons.buffer, option);
            resolve(respons);
          };
        } catch (err) {
          reject(err);
        }
      });
    })
  }

  public serializingData(res: http.IncomingMessage, data: Buffer, option: RequestOption) {
    let info = JSON.stringify(data.toString("utf8"));
    try {
      if (option.forceDeserialization || res.headers?.["content-type"]?.includes("application/json")) {
        info = JSON.parse(info);
        while (option.repeatDeserialization && typeof info === 'string') {
          info = JSON.parse(info);
        }
        return JSON.parse(info);
      } else {
        return info;
      }
    } catch (err) {
      return info;
    }
  }

  /**
   * 运行任务队列,任务队列主要是延时队列
   */
  public async runTaskQueue(info: typeof Frequency.typeof) {
    if (info.lock) {
      return
    }
    let i = 1;
    // 打开锁
    info.lock = true;
    while (info.lock) {
      let task = info.taskQueue.shift();
      await task(i++);
      if (info.taskQueue.length == 0) {
        // 关闭锁
        info.lock = false;
      }
    }
  }

  /**
   * 延迟执行器
   * 判断是哪一种延迟，加入对应的延迟队列
   */
  public async delayActuator(option: typeof RequestOption.typeof): Promise<typeof Response.typeof> {
    // 执行延迟
    if (option.frequency != undefined) {
      return await this.localDelay(option);
    }
    // 正则匹配延迟
    let res: typeof Response.typeof | null = await this.regexFrequencyDelay(option);
    if (res != null) {
      return res;
    }
    // 获取路径延迟时间
    res = await this.pathDelay(option);
    if (res != null) {
      return res;
    }
    // 获取全局延迟
    res = await this.globalDelay(option);
    return res;
  }

  /**
   * 进行全局延迟
   */
  public globalDelay(option: typeof RequestOption.typeof): Promise<typeof Response.typeof> {
    return new Promise((res, reject) => {
      this.globalFrequency.taskQueue.push(async () => {
        return await this.delay(this.globalFrequency, option).then(res).catch(reject);
      })
      this.runTaskQueue(this.globalFrequency);
    })
  }

  /**
   * 路径延迟
   */
  public pathDelay(option: typeof RequestOption.typeof): Promise<typeof Response.typeof | null> {
    return new Promise((res, rejects) => {
      let key = new URL(option.url);
      // 判断是否是路径延迟
      let info: typeof Frequency.typeof = this.localFrequencyMap[key.origin + key.pathname];
      if (!info) {
        // 判断是否是域名延迟
        info = this.localFrequencyMap[key.origin];
      }
      if (info) {
        info.taskQueue.push(async () => {
          return this.delay(info, option).then(res).catch(rejects)
        })
        this.runTaskQueue(info);
      }
      return res(null);
    })

  }

  /**
   * 正则匹配延迟
   */
  public regexFrequencyDelay(option: typeof RequestOption.typeof): Promise<typeof Response.typeof | null> {
    return new Promise((res, reject) => {
      if (option.regexFrequency) {
        // 查找正则频率
        for (let regex of Object.keys(this.localRegexFrequencyMap)) {
          if (option.url.match(regex)) {
            // 找到了
            let info = this.localRegexFrequencyMap[regex];
            info.taskQueue.push(async () => {
              // 延时结束
              return await this.delay(info, option).then(res).catch(reject);
            })
            this.runTaskQueue(info);
          }
        }
      }
      return res(null);
    })

  }

  /**
   * 单次延迟
   */
  public async localDelay(option: typeof RequestOption.typeof): Promise<typeof Response.typeof> {
    return new Promise(async (res, reject): Promise<any> => {
      Error.stackTraceLimit = 17;
      // @ts-ignore
      let key: string = Error().stack.toString().match(/(?<=\()([^)]+)[^()]*?(?=\)$)/)?.[0];
      let info = this.localFrequencyMap[key]
      if (!info) {
        this.localFrequencyMap[key] = info = new Frequency();
        info.frequency = option.frequency as number;
        // 直接执行
        this.executor(option).then(res).catch(reject);
      }
      info.taskQueue.push(async () => {
        // 延时结束
        return await this.delay(info, option).then(res).catch(reject);
      })
      this.runTaskQueue(info);
    })
  }


  /**
   * 延时
   */
  public async delay(info: typeof Frequency.typeof, option: typeof RequestOption.typeof): Promise<typeof Response.typeof> {
    // 在这里执行延时
    await new Promise(res => setTimeout(res, info.lastTimeRequestTime - Date.now()))
    // 执行真正的请求
    let data = await this.executor(option)
    // 设置下次延时
    info.lastTimeRequestTime = Date.now() + (info.frequency * 1000);
    // 延时结束
    return data;
  }


  public new() {
    return new Cjc();
  }

  /**
   * 
   * @param url 
   * @param data 
   * @param option 
   * @returns 
   */
  public get(url: string, data: typeof RequestOption.typeof | object, option: typeof RequestOption.typeof): Promise<typeof Response.typeof> {
    return this.requestMode(url, data, option, "GET");
  }

  public post(url: string, data: typeof RequestOption.typeof | object, option: typeof RequestOption.typeof): Promise<typeof Response.typeof> {
    return this.requestMode(url, data, option, "POST");
  }

  public delete(url: string, data: typeof RequestOption.typeof | object, option: typeof RequestOption.typeof): Promise<typeof Response.typeof> {
    return this.requestMode(url, data, option, "DELETE");
  }

  public put(url: string, data: typeof RequestOption.typeof | object, option: typeof RequestOption.typeof): Promise<typeof Response.typeof> {
    return this.requestMode(url, data, option, "PUT");
  }

  public upload(url: string, data: typeof RequestOption.typeof | object, option: typeof RequestOption.typeof): Promise<typeof Response.typeof> {
    return this.requestMode(url, data, option, "POST", (option) => {
      if (!option.headers) {
        option.headers = {};
      }
      option.headers["content-type"] = this.FORM_DATA;
      return option;
    });
  }

  private requestMode(url: string, data: typeof RequestOption.typeof | object, option: typeof RequestOption.typeof, method: string, callback: (option: typeof RequestOption.typeof) => typeof RequestOption.typeof = (e) => e): Promise<typeof Response.typeof> {
    if (!data) {
      data = {};
      option = {} as RequestOption;
      // @ts-ignore
    } else if ((data["headers"] && option == undefined) || data["$option"]) {
      option = data as RequestOption;
      data = {};
    } else if (!option) {
      option = { method: method } as RequestOption;
    }
    option.method = method;
    option.url = url;
    option.data = data as any;
    callback(option)
    return this.request(option)
  }
}

export = new Cjc();


